AI项目实战(8)AI 视频生成 Agent

《AI Agent 实战》系列 · AI 视频生成 Agent

Posted by Ryan on 2026-06-30
Estimated Reading Time 23 Minutes
Words 5.3k In Total
Viewed Times

周五晚上十一点,自媒体博主小林还窝在工位上。这周他得交一条两分钟的科技科普视频,流程他闭着眼都能走:先写脚本,再手画分镜,然后钻进衣柜里录旁白(图个隔音),最后剪到凌晨两点。一个人活成了一条流水线——累,且慢。

隔壁市场部更头疼。产品要上新,想做个演示视频,找外包一问:两周起步,五位数报价,改一版还得排队。

小林盯着屏幕发愣时冒出一个念头:写脚本、画分镜、配配音、剪片子,这四件事 AI 其实都会。缺的不是能力,是一个把它们串起来的"人"。要是有个 Agent 能当导演,调度编剧、配音、剪辑这些工具,自己只管甩一句"做个两分钟的 Python 介绍视频"——那该多好?

这一章,我们就来造这个导演。

全书主线进度:前面的项目都是"单一步骤"的任务——查订单、搜知识库、审查代码。现在我们要让 Agent 处理多步骤流水线——脚本→分镜→配音→合成,四步环环相扣,出错还要能重试或跳过。这是"单 Agent 越来越能干"的关键一步。

主讲能力:多模态工具编排 + 流水线状态管理
业务场景:用户输入视频主题,Agent 协调脚本创作、分镜设计、配音生成、视频合成全流程。
技术栈:LangChain create_agent + 多个 Claude API 调用 + 流水线状态追踪


7.1 小林的苦衷

7.1.1 业务背景

短视频是当下最主流的内容形式。可一条视频背后,藏着四种手艺:策划脚本的编剧脑、设计分镜的美术眼、录制配音的播音嗓、合成画面的剪辑手。让一个人同时凑齐这四样,难;让四个人协同,慢。

AI 的妙处在于:这四种能力它都有,缺的只是把它们按顺序"排戏"的那个导演。而这,恰恰是 Agent 的本职。

7.1.2 用户故事

编号 作为 我想要 以便
US-1 自媒体创作者 输入主题就能得到一段完整视频 降低视频制作门槛
US-2 企业培训师 把培训大纲转成视频教程 批量生产培训内容
US-3 项目经理 能实时看到制作流水线的进度 知道哪一步出问题了

7.1.3 功能性需求

  • FR-1 脚本创作:根据主题创作分场景脚本
  • FR-2 分镜生成:提取每个场景的 AI 图片 prompt
  • FR-3 配音生成:将旁白文本转为音频文件
  • FR-4 视频合成:将画面和配音合成为最终视频
  • FR-5 进度追踪:实时查看流水线各步骤状态

7.2 画个样子:它该长啥样

把这条流水线画出来,长这样:

%%{init: {'theme':'base','flowchart':{'useMaxWidth':true,'htmlLabels':true}}}%%
graph LR
    Topic["用户输入主题"] --> Script["create_script
创作脚本"] Script --> Board["generate_storyboard
生成分镜"] Board --> Voice["generate_voiceover
生成配音"] Voice --> Compose["compose_video
合成视频"] Compose --> Output["最终视频文件"] classDef s fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#0d47a1 classDef t fill:#e0f7fa,stroke:#00acc1,stroke-width:2px,color:#006064 class Topic,Output s class Script,Board,Voice,Compose t

盯紧这张图——它不是一次大模型调用,而是四个工具在接力。Agent 站在中间当导演,喊一声"开拍",编剧工具先上场,写完剧本递给分镜工具;分镜画完递给配音工具;配音录完递给剪辑工具。哪一步卡壳了,导演还能喊停、重拍,或者干脆跳过这一条接着往下走。

金句:Agent 不是全能选手,而是最好的调度员。 它的本事不在样样精通,而在把对的人安排到对的工位上。


7.3 拆开看:怎么造出来

7.3.1 技术选型

技术点 选型 理由
Agent create_agent 统一
脚本创作 Claude Sonnet(专用 prompt) 高品质文本生成
分镜提取 Claude Haiku(轻量任务) 结构化提取,成本低
配音 预留给真实 TTS API(如 ElevenLabs) 当前模拟实现
视频合成 预留给 FFmpeg 或其他合成工具 当前模拟实现
状态追踪 VideoPipelineState 类 每步独立追踪,可观测

💡 顿悟时刻:很多人第一次做"AI 视频",本能反应是"找一个会生成视频的大模型,一句话出片"。真去试就会栽跟头——这条路又贵又不靠谱。原因很简单:视频生成本质是多个工具按序协作,不是一次大模型调用。 脚本要文本能力(Sonnet),分镜要结构化提取(Haiku 又快又省),配音要 TTS,合成要 FFmpeg。没有一个模型样样精通,但每个环节都有最合适的工具。Agent 的价值,就是当那个把工具排成一条龙的导演。

7.3.2 关键设计:流水线状态管理

导演得随时摸清剧组进度:哪场戏拍完了,哪场卡住了,哪场跳过了。这就是 VideoPipelineState 干的事——它是导演手里的场记板。

1
2
3
4
5
6
class VideoPipelineState:
def update(self, step: str, status: str, output: str = ""):
self.steps[step] = {"status": status, "output": output, "timestamp": ...}

def summary(self) -> str:
# 返回 ✅🔄❌ 图标化的进度摘要

7.3.3 关键设计:模拟 vs 真实服务

先把丑话说前头:本项目的 generate_voiceovercompose_video 用的是模拟实现——生成的是文本占位文件,不是真能播放的 mp3、mp4。这是有意为之:真实的 TTS(如 ElevenLabs)和视频合成(如 FFmpeg)要额外的 API Key 和系统依赖,塞进一个"即装即跑"的教学项目里反而不友好。

但接口是预留好的:函数签名不动,把内部的 write_text 换成真实 API 调用,Agent 侧一行都不用改。换句话说,这是个骨架已经搭好、肌肉等你填的教学项目。


7.4 动手写:三层架构完整代码

7.4.1 架构分层总览

这套导演系统分五层,各司其职:

层级 文件名 职责 核心类/函数
领域模型层 models.py 流水线状态、脚本、分镜等数据结构 PipelineState, StepResult, VideoScript, Storyboard
提示词层 prompts.py 脚本创作、分镜提取的提示词模板 build_script_prompt(), build_storyboard_prompt()
工具层 tools.py 各步骤的工具函数(脚本、分镜、配音、合成) create_script(), generate_storyboard(), get_pipeline_status()
服务层 service.py 流水线编排、重试、降级策略 VideoProductionService
项目入口层 project.py Agent 构建、项目注册 VideoGenerationProject

7.4.2 models.py - 领域模型层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
"""P05 AI 视频生成 Agent - 领域模型层"""
from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Any


class PipelineStatus(str, Enum):
"""流水线步骤状态。"""
PENDING = "pending"
RUNNING = "running"
SUCCESS = "success"
FAILED = "failed"
SKIPPED = "skipped"


@dataclass
class StepResult:
"""单个步骤的执行结果。"""
step_name: str
status: PipelineStatus
output: str = ""
error_message: str = ""
start_time: str = field(default_factory=lambda: datetime.now().isoformat())
end_time: str = ""
duration_seconds: float = 0.0
metadata: dict[str, Any] = field(default_factory=dict)

@property
def is_success(self) -> bool:
return self.status == PipelineStatus.SUCCESS


@dataclass
class VideoScript:
"""视频脚本。"""
topic: str
duration_minutes: int
scenes: list[dict[str, Any]] = field(default_factory=list)
voiceover_text: str = ""
style: str = "professional"


@dataclass
class Storyboard:
"""分镜表。"""
script_id: str = ""
scenes: list[dict[str, Any]] = field(default_factory=list)

def add_scene(self, scene_number: int, prompt: str, duration: int = 5) -> None:
self.scenes.append({
"scene_number": scene_number,
"prompt": prompt,
"duration_seconds": duration,
})


@dataclass
class PipelineState:
"""视频制作流水线状态。"""
topic: str = ""
steps: dict[str, StepResult] = field(default_factory=dict)
final_video_path: str = ""

def update_step(self, step: str, status: PipelineStatus,
output: str = "", error: str = "") -> None:
"""更新步骤状态。"""
if step not in self.steps:
self.steps[step] = StepResult(step_name=step, status=status)
self.steps[step].status = status
self.steps[step].output = output
self.steps[step].error_message = error

def mark_step_end(self, step: str) -> None:
"""标记步骤结束,计算耗时。"""
if step in self.steps:
self.steps[step].end_time = datetime.now().isoformat()
start = datetime.fromisoformat(self.steps[step].start_time)
end = datetime.fromisoformat(self.steps[step].end_time)
self.steps[step].duration_seconds = (end - start).total_seconds()

def get_progress_percent(self) -> int:
"""获取总体进度百分比。"""
if not self.steps:
return 0
completed = sum(1 for s in self.steps.values()
if s.status in (PipelineStatus.SUCCESS, PipelineStatus.SKIPPED))
return int(completed / len(self.steps) * 100)

def summary_markdown(self) -> str:
"""生成状态摘要(Markdown 格式)。"""
status_emojis = {
PipelineStatus.SUCCESS: "✅",
PipelineStatus.RUNNING: "🔄",
PipelineStatus.FAILED: "❌",
PipelineStatus.SKIPPED: "⏭️",
}
step_names = {
"script": "脚本创作",
"storyboard": "分镜生成",
"voiceover": "配音生成",
"video_compose": "视频合成",
}
lines = ["## 🎬 视频制作流水线状态", "",
f"**主题**: {self.topic or '未设置'}",
f"**进度**: {self.get_progress_percent()}%", "",
"### 各步骤状态:", ""]
for step_key, name in step_names.items():
step = self.steps.get(step_key)
if step:
emoji = status_emojis.get(step.status, "⏳")
lines.append(f"{emoji} **{name}** ({step.duration_seconds:.1f}s)")
if step.error_message:
lines.append(f" 错误: {step.error_message}")
else:
lines.append(f"⏳ **{name}** (未开始)")
return "\n".join(lines)

核心代码讲解

  • 状态机设计:每个步骤独立追踪,像剧组的场记单,哪场戏可以单独重拍、单独跳过,互不连坐
  • 富进度信息:不光记状态,还记执行时间、错误信息、输出结果——导演要的就是这种细颗粒度
  • Emoji 友好输出summary_markdown() 直接生成用户能看懂的进度报告,✅🔄❌ 一眼分明
  • 幂等更新update_step() 可以反复调用,不会把已经记下的数据冲掉

7.4.3 prompts.py - 提示词层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
"""P05 AI 视频生成 Agent - Prompt 层"""
from __future__ import annotations

SCRIPT_PROMPT_TEMPLATE = """请为一段 {duration_minutes} 分钟的短视频创作详细脚本。

**主题**: {topic}

## 输出格式要求

请输出 Markdown 格式,严格遵守以下结构:

# 视频脚本

## 分场景脚本

### 场景 1
**画面描述 (English)**: A visually striking opening shot...

**旁白文本 (中文)**: 大家好,今天我们要聊的是...

**建议时长**: 15 秒

...

## 完整旁白(配音用)
(将所有场景的旁白合并成一段连续的文本)

## 创作要求
1. **场景数量**: {scenes_count} 个场景(每个 10-20 秒)
2. **画面描述**: 用英文,适合作为 AI 图片生成的 prompt,包含景别、光线
3. **旁白文本**: 用中文,口语化、自然,适合朗读
4. **画面描述技巧**: 要包含主体描述、景别、光线、画质、风格
"""


def build_script_prompt(topic: str, duration_minutes: int) -> str:
"""构建脚本创作提示词。"""
scenes_count = max(5, int(duration_minutes * 4))
return SCRIPT_PROMPT_TEMPLATE.format(
topic=topic,
duration_minutes=duration_minutes,
scenes_count=scenes_count,
)


STORYBOARD_PROMPT_TEMPLATE = """请从以下视频脚本中提取每个场景的画面描述,转换为 AI 图片生成 prompt。

## 脚本内容

{script_text}

## 输出要求

请输出 **纯 JSON 数组**,不要其他文字,不要 markdown 标记。格式如下:

[
{{
"scene_number": 1,
"prompt": "Wide shot of modern office, cinematic lighting, 4K, professional",
"duration_seconds": 15
}}
]

每个 prompt 必须是英文,包含:景别、光线、画质、风格。
"""


def build_storyboard_prompt(script_text: str) -> str:
"""构建分镜提取提示词。"""
return STORYBOARD_PROMPT_TEMPLATE.format(script_text=script_text[:4000])

核心代码讲解

  • 格式契约明确:每个 prompt 都把输出格式(Markdown / JSON)写得明明白白,降低解析失败率——给 AI 定规矩,比事后修格式省心
  • 长度保护:脚本超过 4000 字自动截断,防止 Token 溢出把后续步骤拖崩
  • 结构化输出引导:直接甩一段 JSON 样例给 AI 看,比干巴巴说"请输出 JSON"有效得多

7.4.4 tools.py - 工具层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
"""P05 AI 视频生成 Agent - 工具层"""
from __future__ import annotations

import json
import re
import time
from pathlib import Path

from langchain.tools import tool

from core.config import DATA_DIR
from core.logging_conf import get_logger

from .models import PipelineState, PipelineStatus
from .prompts import build_script_prompt, build_storyboard_prompt

logger = get_logger("p05.video_generation.tools")
VIDEO_OUTPUT_DIR = DATA_DIR / "videos"
VIDEO_OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

# 全局流水线状态
_pipeline = PipelineState()


def _call_llm_for_script(prompt: str) -> str:
"""调用 LLM 生成脚本。生产环境替换为真实的 Claude API 调用。"""
try:
from anthropic import Anthropic
client = Anthropic()
resp = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
messages=[{"role": "user", "content": prompt}],
)
return resp.content[0].text
except ImportError:
# 开发环境模拟返回
return "# 视频脚本\n\n## 场景 1\n**画面描述**: Test scene\n**旁白**: 测试旁白"


@tool
def create_script(topic: str, duration_minutes: int = 3) -> str:
"""根据主题创作视频脚本。"""
logger.info("开始创建脚本: topic='%s'", topic)
_pipeline.topic = topic
_pipeline.update_step("script", PipelineStatus.RUNNING)

try:
prompt = build_script_prompt(topic, duration_minutes)
script = _call_llm_for_script(prompt)

_pipeline.update_step("script", PipelineStatus.SUCCESS,
output=f"脚本已生成,长度 {len(script)} 字符")
_pipeline.mark_step_end("script")
return script
except Exception as e:
_pipeline.update_step("script", PipelineStatus.FAILED, error=str(e))
_pipeline.mark_step_end("script")
return f"脚本创作失败: {e}"


@tool
def generate_storyboard(script: str) -> str:
"""根据脚本生成分镜表。"""
logger.info("开始生成分镜表")
_pipeline.update_step("storyboard", PipelineStatus.RUNNING)

try:
prompt = build_storyboard_prompt(script)
storyboard_json = _call_llm_for_script(prompt) # 复用 LLM 调用函数

# 提取并解析 JSON
json_match = re.search(r'\[.*\]', storyboard_json, re.DOTALL)
if json_match:
scenes = json.loads(json_match.group(0))
_pipeline.update_step("storyboard", PipelineStatus.SUCCESS,
output=f"分镜表已生成,共 {len(scenes)} 个场景")
else:
_pipeline.update_step("storyboard", PipelineStatus.SUCCESS,
output="分镜表已生成(JSON 解析未确认)")
_pipeline.mark_step_end("storyboard")
return storyboard_json
except Exception as e:
_pipeline.update_step("storyboard", PipelineStatus.FAILED, error=str(e))
_pipeline.mark_step_end("storyboard")
return f"分镜生成失败: {e}"


@tool
def generate_voiceover(script_text: str, output_filename: str = "voiceover.mp3") -> str:
"""为脚本生成配音。生产环境替换为真实 TTS API。"""
_pipeline.update_step("voiceover", PipelineStatus.RUNNING)
try:
output_path = VIDEO_OUTPUT_DIR / output_filename
# 模拟实现:写入占位文件
output_path.write_text(f"[配音占位] 脚本长度: {len(script_text)}", encoding="utf-8")
_pipeline.update_step("voiceover", PipelineStatus.SUCCESS, output=str(output_path))
_pipeline.mark_step_end("voiceover")
return f"配音已生成并保存到: {output_path}"
except Exception as e:
_pipeline.update_step("voiceover", PipelineStatus.FAILED, error=str(e))
return f"配音生成失败: {e}"


@tool
def compose_video(scenes_json: str, output_filename: str = "final_video.mp4") -> str:
"""将各场景画面和配音合成为最终视频。生产环境替换为 FFmpeg。"""
_pipeline.update_step("video_compose", PipelineStatus.RUNNING)
try:
output_path = VIDEO_OUTPUT_DIR / output_filename
output_path.write_text(f"[视频占位] 场景数: {scenes_json.count('scene')}", encoding="utf-8")
_pipeline.final_video_path = str(output_path)
_pipeline.update_step("video_compose", PipelineStatus.SUCCESS, output=str(output_path))
_pipeline.mark_step_end("video_compose")
return f"视频已合成并保存到: {output_path}"
except Exception as e:
_pipeline.update_step("video_compose", PipelineStatus.FAILED, error=str(e))
return f"视频合成失败: {e}"


@tool
def get_pipeline_status() -> str:
"""查看当前视频制作流水线的进度状态。"""
return _pipeline.summary_markdown()

核心代码讲解

  • 状态自动更新:每个工具函数内部自己更新流水线状态,Agent 不用操心记场记——导演只管喊"开拍",进度单自动填
  • 模拟 vs 真实接口:工具签名稳定,内部实现可从"模拟"切到"真实 API",Agent 侧零改动
  • 容错设计:异常被捕获并记进流水线状态,不会让整条生产线因为一格胶片卡住而全盘崩
  • 进度可查询get_pipeline_status() 让用户随时摸到脉搏

说明:上面这份清单为了聚焦主线做了精简。完整源码里分镜走的是单独的 _call_llm_for_storyboard,用的是 Claude Haiku(和 7.3.1 选型表对得上,又快又省);清单里复用 _call_llm_for_script 只是为演示省篇幅。

⚠️ 避坑:文件路径安全output_filename 是外部传入的参数,直接拼路径就有路径遍历风险——万一传进来一个 ../../etc/passwd,文件就写到外面去了。完整源码 tools.py 里用 _validate_output_filename 把关:禁止文件名包含 /\,再用 Path.resolve() 校验最终路径必须落在 VIDEO_OUTPUT_DIR 内。这份精简清单没展示这段防护,落地时别忘了补上。


7.4.5 service.py - 服务层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
"""P05 AI 视频生成 Agent - 服务层"""
from __future__ import annotations

import time
from typing import Callable

from core.logging_conf import get_logger

from .models import PipelineState, PipelineStatus

logger = get_logger("p05.video_generation.service")


class VideoProductionService:
"""视频制作服务。编排流水线执行、重试、降级。"""

def __init__(self, max_retries: int = 2, skip_on_failure: bool = True):
self.max_retries = max_retries
self.skip_on_failure = skip_on_failure
self.pipeline = PipelineState()

def _execute_step(self, step_name: str, func: Callable, *args, **kwargs) -> any:
"""执行单个步骤,包含重试逻辑。"""
for attempt in range(self.max_retries + 1):
try:
logger.info("执行步骤 '%s',第 %d/%d 次尝试",
step_name, attempt + 1, self.max_retries + 1)
result = func(*args, **kwargs)

if isinstance(result, str) and "失败" in result:
raise Exception(result)

logger.info("步骤 '%s' 执行成功", step_name)
return result
except Exception as e:
logger.warning("步骤 '%s' 第 %d 次尝试失败: %s", step_name, attempt + 1, e)
if attempt < self.max_retries:
wait_seconds = 2 ** attempt # 指数退避
logger.info("等待 %d 秒后重试...", wait_seconds)
time.sleep(wait_seconds)
elif self.skip_on_failure:
logger.warning("跳过步骤 '%s',继续后续流程", step_name)
return None
else:
raise
return None

def create_full_video(self, topic: str, duration_minutes: int = 2) -> PipelineState:
"""执行完整的视频制作流水线。"""
from .tools import (create_script, generate_storyboard,
generate_voiceover, compose_video)

logger.info("开始完整视频制作: topic='%s'", topic)
self.pipeline.topic = topic

# 步骤 1: 创建脚本
script = self._execute_step("script", create_script.invoke,
{"topic": topic, "duration_minutes": duration_minutes})
# 步骤 2: 生成分镜
storyboard = None
if script is not None:
storyboard = self._execute_step("storyboard", generate_storyboard.invoke,
{"script": script})
# 步骤 3: 生成配音
if script is not None:
self._execute_step("voiceover", generate_voiceover.invoke,
{"script_text": script[:2000]})
# 步骤 4: 合成视频
if storyboard is not None:
self._execute_step("video_compose", compose_video.invoke,
{"scenes_json": storyboard})

logger.info("视频制作完成,进度: %d%%", self.pipeline.get_progress_percent())
return self.pipeline

def regenerate_step(self, step_name: str, **kwargs) -> any:
"""重新执行某个步骤。"""
from .tools import (create_script, generate_storyboard,
generate_voiceover, compose_video)
step_funcs = {
"script": create_script.invoke,
"storyboard": generate_storyboard.invoke,
"voiceover": generate_voiceover.invoke,
"video_compose": compose_video.invoke,
}
return self._execute_step(step_name, step_funcs[step_name], kwargs)

核心代码讲解

  • 自动重试_execute_step() 内置指数退避重试(1s→2s→4s),容忍临时故障,像导演给演员多几次 NG 的机会
  • 失败降级:单步失败可跳过继续,不浪费已成功步骤的资源——配音没录成,不代表前面写好的脚本要作废
  • 编排可复用:服务层编排不依赖 Agent,也能作为独立 API 供非 Agent 系统调用
  • 进度钩子:完整源码中 create_full_video 还接受 progress_callback 参数,可扩展为 WebSocket 实时推送进度

⚠️ 避坑:外部 API 调用降级。脚本和分镜都靠 Claude API,网络抖动、限流、余额不足随时会让某一步炸掉。_execute_step 这套"重试 + 跳过"兜底,比"一次失败全盘崩溃"值钱得多——生产环境里,能带着伤继续跑的流水线,才是好流水线。


7.4.6 project.py - 项目入口层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
"""P05 AI 视频生成 Agent - 项目入口层"""
from __future__ import annotations

from langchain.agents import create_agent

from core import BaseProject, build_chat_model, registry
from core.logging_conf import get_logger

from .prompts import build_system_prompt
from .tools import get_available_tools

logger = get_logger("p05.video_generation.project")


class VideoGenerationProject(BaseProject):
"""AI 视频生成 Agent 项目。"""

id = "p05_video_generation"
name = "AI 视频生成 Agent"
description = "多模态工具编排:脚本→分镜→配音→视频合成"
capabilities = ["多模态", "流水线", "LLM 编排"]

def build_agent(self) -> any:
"""构建视频生成 Agent。"""
model = build_chat_model()
tools = get_available_tools()
system_prompt = build_system_prompt()
return create_agent(model=model, tools=tools, system_prompt=system_prompt)


registry.register(VideoGenerationProject())

核心代码讲解

  • 关注点分离:工具层负责单步执行,服务层负责流水线编排,Agent 层负责人机交互理解——导演、剧组、场务各管一摊
  • 多层 API:既支持完整 Agent 交互,也支持直接调用 Service 做自动化批量制作
  • 可插拔架构:工具内部实现可独立替换(模拟 TTS → 真实 TTS → 不同厂商 TTS),换个肌肉不伤骨架

7.5 跑一跑:它真的行吗

测试 验证
test_pipeline_state_tracking 状态追踪正常(✅🔄 图标正确)
test_create_script(集成) 脚本包含"场景"内容
test_basic_workflow(集成) 完整流水线能跑通

7.6 送上线:让它上班

同前——继承 BaseProject,自动接入统一 API。


7.7 回头看:学到了什么

能力 在本项目中的体现
多模态工具编排 脚本(文本) → 分镜(结构提取) → 配音(音频) → 合成(视频)
流水线状态 VideoPipelineState 每步独立追踪
失败降级 某步失败不影响已完成步骤
模拟服务模式 预留真实 API 接口,当前模拟实现

⚠️ 常见坑

  1. 脚本太长,分镜提取超时 — 已做 4000 字截断兜底
  2. 不查看流水线状态 — 用户不知道进度干着急。把状态查询工具化(get_pipeline_status)这件事,看着小,体验上天差地别

📌 项目五完成。 单 Agent 阶段过半——Agent 已经能从"一问一答"进化到"流水线编排"。下一章我们迈进 Multi-Agent 阶段:让多个 Agent 组成团队,分工协作。


如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !